package edu.unc.ils.memai.vocab;

import java.io.File;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import maui.stemmers.Stemmer;

import org.apache.commons.dbcp.ConnectionFactory;
import org.apache.commons.dbcp.DriverManagerConnectionFactory;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingDriver;
import org.apache.commons.io.FileUtils;
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.GenericObjectPool;

import edu.unc.ils.mrc.vocabulary.Relation;

public abstract class Vocabulary 
{
    protected Stemmer stemmer;

    protected List<String> stopwords;

    protected boolean toLowerCase = true;

    protected boolean reorder = true;
    
    protected boolean debugMode = false;
    
    protected int threshold = -1;
    
    public void setThreshold(int threshold)
    {
        this.threshold = threshold;
    }

    public void setLowerCase(boolean toLowerCase) {
        this.toLowerCase = toLowerCase;
    }

    public void setReorder(boolean reorder) {
        this.reorder = reorder;
    }

    public void setStemmer(Stemmer stemmer) {
        this.stemmer = stemmer;
    }
    
    public void setDebug(boolean debugMode) {
        this.debugMode = debugMode;
    }

    /**
     * Set the stopwords class.
     * @param stopwords 
     */
    @SuppressWarnings("unchecked")
    public void setStopwords(List<String> stopwords) {
        this.stopwords = stopwords;
    }
    
    public void setStopwords(String path) throws Exception {
        this.stopwords = FileUtils.readLines(new File(path), "UTF-8");
    }
    
    public abstract void load(String path) throws Exception;
    
    public abstract List<String> getSenses(String phrase) throws Exception;
    
    public abstract String getTerm(String id);
    
    
    Random random = new Random(System.nanoTime());
    Map<Integer, Map<Integer, String>> relationMap = new HashMap<Integer, Map<Integer, String>>();
    Connection connection;
    
    public void open(String path) throws Exception 
    {
        String name = new File(path).getName();
        
        // Initialize an H2 connection pool
        String uri = "jdbc:h2:" + path;
        Class.forName("org.h2.Driver");

        ObjectPool connectionPool = new GenericObjectPool(null);
        ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(uri, "", "");
        new PoolableConnectionFactory(connectionFactory, connectionPool, null, null, false, true);

        Class.forName("org.apache.commons.dbcp.PoolingDriver");
        PoolingDriver driver = (PoolingDriver) DriverManager.getDriver("jdbc:apache:commons:dbcp:");
        driver.registerPool(name, connectionPool);
        connection =  DriverManager.getConnection("jdbc:apache:commons:dbcp:" + name);
    }
    
    protected Connection getConnection() throws SQLException {
        return connection;
    }
    
    public int getNextRandom(int conceptId) throws Exception
    {
        return getNextRandom(conceptId, 1, 1, 1);
    }
    
    public int getNextRandom(int conceptId, int btWeight, int ntWeight, int rtWeight)
        throws Exception
    {
        double total = 0.0;
        
        List<Relation> relations = getRelatedConcepts(conceptId);
        
        // cumulative distribution
        double[] weights = new double[relations.size()];
        int[] concepts = new int[relations.size()];
        int i=0;
        for(Relation relation: relations)
        {
            String relationType = relation.getRelationType();
            concepts[i] = relation.getRelatedConceptId();
            if (relationType.equals("broader"))
                weights[i] = btWeight;
            else if (relationType.equals("narrower"))
                weights[i] = ntWeight;
            else
                weights[i] = rtWeight;
                
            total += weights[i];
            i++;
        }
       
       
        // Weighted random selection
        double choice = random.nextDouble() * total;
        
        int selected = 0;
        for (int j=0; j < i; j++) {
            double weight = weights[j];
            choice -= weight;
            if (choice < 0) {
                selected = j;
                break;
            }
        }
        if (concepts.length == 0) {
            double d = random.nextDouble();
            return (int)(relationMap.size() * d);
        }
        return concepts[selected];
    }
    
    public List<Relation> getRelatedConcepts(int conceptId) throws Exception
    {
        List<Relation> relations = new ArrayList<Relation>();
        
        if (relationMap.size() == 0)
            loadRelatedConcepts();
        
        Map<Integer, String> rels = relationMap.get(conceptId);
        if (rels != null)
        {
            for (Integer conceptId2: rels.keySet())
            {
                String relation = rels.get(conceptId2);
                
                Relation rel = new Relation();
                rel.setConceptId(conceptId);
                rel.setRelatedConceptId(conceptId2);
                rel.setRelationType(relation);
                relations.add(rel);
            }
        }
        return relations;
    }
    
    public void loadRelatedConcepts() throws Exception
    {
        //System.out.println("Loading concepts");
        Connection con = getConnection();

        Statement s = con.createStatement();
        ResultSet rs = s.executeQuery("select concept_id_1, concept_id_2, relation from concept_relation");
        while (rs.next())
        {
            int conceptId1 = rs.getInt(1);
            int conceptId2 = rs.getInt(2);
            String relation = rs.getString(3);
            
            Map<Integer, String> relations = relationMap.get(conceptId1);
            if (relations == null)
                relations = new HashMap<Integer, String>();
            
            relations.put(conceptId2, relation);
            relationMap.put(conceptId1, relations);
        }
        rs.close();
        s.close();
    }
    public int getSize() throws Exception {
        int size = 0;
        
        Connection con = getConnection();
        
        Statement s = con.createStatement();
        ResultSet rs = s.executeQuery("select count(*) from concept");
        if (rs.next())
            size = rs.getInt(1);
        rs.close();
        s.close();
        
        return size;
    }
    public int getConceptId(String prefLabel) throws SQLException
    {
        Connection con = getConnection();
        return getConceptId(con, prefLabel);
    }
    
    protected int getConceptId(Connection con, String prefLabel) throws SQLException
    {
        int conceptId = -1;
        PreparedStatement ps = con.prepareStatement("select concept_id from concept where pref_label_lower = ?");
        ps.setString(1, prefLabel.toLowerCase());
        
        ResultSet rs = ps.executeQuery();
        if (rs.next())
            conceptId = rs.getInt(1);
        
        ps.close();
        rs.close();
        
        return conceptId;
    }
    
    public String getPrefLabel(int conceptId) throws SQLException
    {
        Connection con = getConnection();
        String prefLabel = null;
        PreparedStatement ps = con.prepareStatement("select pref_label from concept where concept_id = ?");
        ps.setInt(1, conceptId);
        
        ResultSet rs = ps.executeQuery();
        if (rs.next())
            prefLabel = rs.getString(1);
        
        ps.close();
        
        return prefLabel;
    }
}
